grammar Chaos;

options { language=Java; backtrack=true;}

@header {
import java.util.ArrayList;
import java.util.HashMap;
}

@members {
	private class Stack
	{
		public ArrayList<HashMap<String,String>> lst = new ArrayList<HashMap<String,String>>();
		public void push(HashMap hm)
		{
			lst.add(hm);
		}
		public HashMap pop()
		{
			return lst.remove(lst.size()-1);
		}
		public String Get(String key)
		{
			for(int i = lst.size()-1; i>=0; i--)
			{
				if(lst.get(i).containsKey(key))
				{
					return lst.get(i).get(key);
				}
			}
			return null;
		}
		public void Set(String key, String value)
		{
			lst.get(lst.size()-1).put(key,value);
		}
	}
	public Stack stk = new Stack();
}

INT
	: ('0'..'9')+
	;

BASETYPE
	: 'int'
	| 'void'
	;

ID
	: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
	;

WS
	: (' '|'\t'|'\r'|'\n')+ { $channel=HIDDEN; }
	;

program returns [String LLVM]
	:
	{
		stk.push(new HashMap<String,String>());
	}
	 ( global[stk,0]
	 	{
			if($LLVM == null)
				$LLVM = $global.LLVM;
			else
				$LLVM += $global.LLVM;
		}
	  )* EOF 
	;

global [Stack stk, int Index] returns [String LLVM]
	: funcDefine[$stk,$Index] {$LLVM = $funcDefine.LLVM;}
	| ';'
	;

funcDefine [Stack stk,int Index] returns [String LLVM, int retlableIndex]
	: funcHeader[$stk]{$LLVM = $funcHeader.LLVM;} 
	block[$stk,$Index, true, true]
	{
		$LLVM += $block.LLVM;
		//System.out.println($block.retIndex);
	}
	;

funcHeader[Stack stk] returns [String LLVM]
@init{
	String retType = new String();
}
	:
	{
		$LLVM = "\ndefine ";
	}
	BASETYPE 
	{
		//System.out.println($BASETYPE.text+"a");
		if($BASETYPE.text.equals("int"))
		{
			$LLVM += "i32";
			retType = "i32";
		}
		if($BASETYPE.text.equals("void"))
		{
			$LLVM += "void";
			retType = "void";
		}
	}
	ID 
	{
		$stk.Set($ID.text,retType);
		$LLVM += " @";
		$LLVM += $ID.text;
	}
	'('
	{
		$LLVM += "(";
	}
	( (pa=singleParam{$LLVM += $pa.LLVM;})((','{$LLVM += ", ";}) (pb=singleParam {$LLVM += $pb.LLVM;}))* )? 
	')'
	{
		$LLVM += ") nounwind uwtable";
	}
	;

//todo Add to Table;
singleParam returns [String LLVM]
	: BASETYPE 
	{
		if($BASETYPE.text.equals("int"))$LLVM = "i32";
		if($BASETYPE.text.equals("void"))$LLVM = "void";
	} 
	('*'{$LLVM=$LLVM+"*";})? ID {$LLVM = $LLVM+" \%"+$ID.text;}
	;

block [Stack stk, int Index, boolean needBrackets, boolean needRet] returns [String LLVM, int retIndex]
	:'{'
	 {
	 	if($needBrackets)
	 	{
	 		$LLVM = "\n{\n; <label>:"+$Index+"\n";
	 		$Index = $Index+1;
	 	}
	 	
	 }
	 (varDeclearation[$stk,$Index]
	 {
	 	$Index = $varDeclearation.retIndex;
	 	if($LLVM == null)
	 		$LLVM = $varDeclearation.LLVM;
	 	else if($varDeclearation.LLVM != null)
	 		$LLVM += $varDeclearation.LLVM;
	 })* 
	 (sentence[$stk,$Index]
	 {
	 	$Index = $sentence.retIndex;
	 	if($LLVM == null)
	 		$LLVM = $sentence.LLVM;
	 	else if($sentence.LLVM != null)
	 		$LLVM += $sentence.LLVM;	
	 })* 
	 '}'
	 {
	 	if($needRet) $LLVM+="\n ret void";
	 	if($needBrackets)$LLVM += "\n}";
	 	$retIndex = $Index;
	 	//System.out.println($retIndex);
	 }
	;

varDeclearation [Stack stk,int Index] returns [String LLVM, int retIndex]
	: BASETYPE 
	a=singleVar[$stk,$BASETYPE.text,$Index]{$Index = $a.retIndex;} 
	(',' b=singleVar[$stk,$BASETYPE.text,$Index]{$Index = $b.retIndex;})* ';'
	{$retIndex = $Index;}
	;

singleVar [Stack stk, String Btype, int Index] returns [String LLVM,int retIndex]
	: '*'? ID ('=' expr[$stk,$Index])?
	|  ID '[' INT? ']' '='  exprGroup[$stk, $Index]
	;

sentence [Stack stk, int Index] returns [String LLVM,int retIndex]
	: expr[$stk, $Index] ';'{$retIndex = $Index;}
	| block[$stk, $Index, false, false]{$retIndex = $Index;}
	| 'return' expr[$stk, $Index] ';'{$retIndex = $Index;}
	| 'if' '(' expr[$stk, $Index] ')' sentence[$stk,$Index]{$retIndex = $Index;}
	| 'for' '(' expr[$stk, $Index] ';' expr[$stk, $Index] ';' expr[$stk, $Index] ')' sentence[$stk, $Index]{$retIndex = $Index;}
	;


expr [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	: exprlv6[$stk, $Index] opexprlv14[$stk, $Index]  
	{
		$retIndex = 99;
		$addr = "999";
		stk.Set("999","i32*");
	}
	;
opexprlv14[Stack $stk, int Index]
	: '=' exprlv6[$stk, $Index]
	| {}
	;
	
exprlv6[Stack stk, int Index]
	: exprlv4[$stk, $Index] opexprlv6[$stk, $Index]
	;

opexprlv6[Stack stk, int Index]
	: '<' exprlv4[$stk, $Index]
	| '>' exprlv4[$stk, $Index]
	| {}
	;

exprlv4 [Stack stk, int Index]
	: exprlv2[$stk, $Index] opexprlv4[$stk, $Index]
	;

opexprlv4 [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	: 
	'+' a=exprlv2[$stk, $Index] b=opexprlv4[$stk, $Index]
	{

	}
	| 
	'-' c=exprlv2[$stk, $Index] d=opexprlv4[$stk, $Index]
	{

	}
	| 
	{
		$LLVM = "";
		$addr = "";
		$retIndex = $Index;
	}
	;

exprlv2 [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	: 
	(ID '++'
	{
		$stk.Set($Index+"", $stk.Get($ID.text));
		$LLVM = "\%"+$Index+" = load "+stk.Get($ID.text)+"* \%"+$ID.text+"\n";
		$Index++;
		$stk.Set($Index+"", $stk.Get($ID.text));
		$LLVM += "\%"+$Index+" = add nsw "+$stk.Get($Index+"")+" \%"+ ($Index-1) + ", 1\n";
		$LLVM += "store "+$stk.Get($Index+"")+" \%"+$Index+", "+$stk.Get($ID.text)+"* \%"+$ID.text+"\n";
		$Index++;
		$addr = $ID.text;
		$retIndex = $Index;
		System.out.println($LLVM);
	})
	|
	('&' a=exprlv2[$stk, $Index]
	{
		$Index = $a.retIndex;
		$LLVM = $a.LLVM;
		$stk.Set($Index+"", $stk.Get($a.addr)+"*");
	 	$LLVM += "\%"+$Index+" = alloca "+stk.Get($a.addr)+"\n";
	 	$LLVM += "store "+stk.Get($a.addr)+" \%"+$a.addr+", "+stk.Get($a.addr)+"* "+"\%"+$Index+"\n";
	 	$addr = $Index+"";
	 	$Index++;
	 	$retIndex = $Index;
	 	//System.out.println($LLVM);
	})
	|
	('*' b=exprlv2[$stk, $Index]
	{
		$Index = $b.retIndex;
		$LLVM = $b.LLVM;
		$stk.Set($Index+"", $stk.Get($b.addr).substring(0,$stk.Get($b.addr).length()-1));
		$LLVM += "\%"+$Index+" = load "+stk.Get($b.addr)+" \%"+$b.addr+"\n";
		$addr = $Index+"";
		$Index++;
		$retIndex = $Index;
		//System.out.println($LLVM);
	})
	|
	('-' c=exprlv2[$stk, $Index]
	{
		$Index = $c.retIndex;
		$LLVM = $c.LLVM;
		$stk.Set($Index+"", $stk.Get($c.addr).substring(0,$stk.Get($c.addr).length()-1));
		$LLVM += "\%"+$Index+" = load "+stk.Get($c.addr)+" \%"+$c.addr+"\n";
		$Index++;
		$stk.Set($Index+"", $stk.Get(($Index-1)+""));
		$LLVM += "\%"+$Index+" = mul "+stk.Get(($Index-1)+"")+" \%"+($Index-1)+", -1\n";
		$LLVM += "store "+$stk.Get($Index+"")+" \%"+$Index+", "+$stk.Get($c.addr)+" \%"+$c.addr+"\n";
		$Index++;
		$retIndex = $Index;
		$addr=$c.addr;
		//System.out.println($LLVM);
	})
	| 
	'!' d=exprlv2[$stk, $Index]
	{
		System.out.println();
		$Index = $d.retIndex;
		$LLVM = $d.LLVM;
		$stk.Set($Index+"", $stk.Get($d.addr).substring(0,$stk.Get($d.addr).length()-1));
		$LLVM += "\%"+$Index+" = load "+stk.Get($d.addr)+" \%"+$d.addr+"\n";
		$Index++;
		$stk.Set($Index+"", $stk.Get(($Index-1)+""));
		$LLVM += "\%"+$Index+" = xor "+stk.Get(($Index-1)+"")+" \%"+($Index-1)+", -1\n";
		$LLVM += "store "+$stk.Get($Index+"")+" \%"+$Index+", "+$stk.Get($d.addr)+" \%"+$d.addr+"\n";
		$Index++;
		$retIndex = $Index;
		$addr=$d.addr;
		//System.out.println($LLVM);
	}
	| 
	(exprlv1[$stk, $Index]
	{
		$LLVM = $exprlv1.LLVM;
		$addr = $exprlv1.addr;
		$retIndex = $exprlv1.retIndex;
	})
	;


exprlv1 [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	:(atom[$stk, $Index]){$Index = $atom.retIndex;} array[$stk, $Index]
	{
		if($array.addr == "")
		{
			$LLVM = $atom.LLVM;
			$retIndex = $atom.retIndex;
			$addr = $atom.addr;
		}
		else
		{
			if($atom.LLVM != null)
				$LLVM = $atom.LLVM;
			if($LLVM == null)
				$LLVM = $array.LLVM;
			else
			{
				if($array.LLVM != null)
					$LLVM += $array.LLVM;
			}
			$Index = $array.retIndex;
			String temp = new String();
			$stk.Set($Index+"", $stk.Get($atom.addr).substring(0, $stk.Get($atom.addr).length()-1));
			temp = "\%"+$Index+" = load "+stk.Get($atom.addr)+" \%"+$atom.addr+"\n";
			Index++;
			$stk.Set($Index+"", $stk.Get($array.addr).substring(0, $stk.Get($array.addr).length()-1));
			temp += "\%"+$Index+" = load "+$stk.Get($array.addr)+" \%"+$array.addr+"\n";
			Index++;
			$stk.Set($Index+"", $stk.Get(($Index-2)+""));
			temp += "\%"+$Index+" = getelementptr inbounds "+$stk.Get(($Index-2)+"")+" \%"+($Index-2)+", "+$stk.Get(($Index-1)+"")+" \%"+($Index-1)+"\n";
			$addr = $Index + "";
			Index++;
			if($LLVM == null)
				$LLVM = temp;
			else
				$LLVM += temp;
			$retIndex = $Index;
			//System.out.println(temp);
		}
	}
	;

array [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	:'[' expr[$stk, $Index] ']'
	{
		$LLVM = $expr.LLVM;
		$addr = $expr.addr;
		$retIndex = $expr.retIndex;
	}
	|
	{
		$LLVM = "";
		$addr = "";
		$retIndex = $Index;
	}
	;

atom [Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
	: INT
	 {
	 	$stk.Set($Index+"","i32*");
	 	$LLVM = "\%"+$Index+" = alloca i32\n";
	 	$LLVM += "store i32 " + $INT.text+", i32* \%"+$Index+"\n";
	 	$addr = $Index+"";
	 	$Index++;
	 	$retIndex = $Index;
	 }
	| functionCall[$stk, $Index]
	 {
	 	$LLVM = $functionCall.LLVM;
	 	$addr = $functionCall.addr;
	 	$retIndex = $functionCall.retIndex;
	 }
	| ID
	 {
	 	$stk.Set($Index+"", stk.Get($ID.text)+"*");
	 	$LLVM = "\%"+$Index+" = alloca "+stk.Get($ID.text)+"\n";
	 	$LLVM += "store "+stk.Get($ID.text)+" \%"+$ID.text+", "+stk.Get($ID.text)+"* "+"\%"+$Index+"\n";
	 	$addr = $Index+"";
	 	$Index++;
	 	$retIndex = $Index;
	 	//System.out.println($LLVM);
	 }
	| '(' expr[$stk, $Index]')'
	 {
	 	$LLVM = $expr.LLVM;
	 	$addr = $expr.addr;
	 	$retIndex = $expr.retIndex;
	 }
	;

functionCall[Stack stk, int Index] returns [String LLVM, String addr, int retIndex]
@init{
	String add = new String();
}
	: ID
	{
		if(!($stk.Get($ID.text).equals("void")))
		{
			add = "\%"+$Index + " = call " + $stk.Get($ID.text) + "@" + $ID.text+"(";
			$stk.Set($Index+"", $stk.Get($ID.text));
			$addr = $Index+"";
			$Index++;
		}
		else
		{
			add = "call " + $stk.Get($ID.text) + "@" + $ID.text+"(";
		}
	}
	'(' ( 
	a=expr[$stk, $Index]
	{
		//$Index = $a.retIndex;
		if($a.LLVM != null)
			$LLVM = $a.LLVM;
		$stk.Set($Index+"", $stk.Get($a.addr).substring(0,$stk.Get($a.addr).length()-1));
		if($LLVM == null)
			$LLVM = "\%"+$Index+" = load "+$stk.Get($a.addr)+" \%"+$a.addr+"\n";
		else
			$LLVM += "\%"+$Index+" = load "+$stk.Get($a.addr)+" \%"+$a.addr+"\n";
		add += $stk.Get($Index+"")+" \%"+$Index;
		$Index++;
	}
	(',' 
	b=expr[$stk, $Index]
	{
		//$Index = $b.retIndex;
		if($b.LLVM != null)
			$LLVM += $b.LLVM;
		$stk.Set($Index+"", $stk.Get($b.addr).substring(0,$stk.Get($b.addr).length()-1));
		$LLVM += "\%"+$Index+" = load "+$stk.Get($b.addr)+" \%"+$b.addr+"\n";
		add += ", "+$stk.Get($Index+"")+" \%"+$Index;
		$Index++;
	}
	)* )? ')'
	{
		add+=")\n";
		$LLVM += add;
		if(add.substring(0,1).equals("\%"))
		{
			$stk.Set($Index+"", $stk.Get($addr)+"*");
	 		$LLVM += "\%"+$Index+" = alloca "+stk.Get($addr)+"\n";
	 		$LLVM += "store "+stk.Get($addr)+" \%"+$addr+", "+stk.Get($addr)+"* "+"\%"+$Index+"\n";
	 		$addr = $Index+"";
	 		$Index++;
	 		$retIndex = $Index;
		}
		else
		{
			$addr = "null";
			$retIndex = $Index;
		}
		//System.out.println($LLVM);
	}
	;
exprGroup[Stack stk, int Index]
	: '{' INT (',' INT)* '}'
	;
